home *** CD-ROM | disk | FTP | other *** search
/ Amiga Tools 2 / Amiga Tools 2.iso / tools / jade / src / undo.c < prev    next >
C/C++ Source or Header  |  1995-03-09  |  11KB  |  404 lines

  1. /* undo.c -- Recording and use of undo information
  2.    Copyright (C) 1994 John Harper <jsh@ukc.ac.uk>
  3.  
  4.    This file is part of Jade.
  5.  
  6.    Jade is free software; you can redistribute it and/or modify it
  7.    under the terms of the GNU General Public License as published by
  8.    the Free Software Foundation; either version 2, or (at your option)
  9.    any later version.
  10.  
  11.    Jade is distributed in the hope that it will be useful, but
  12.    WITHOUT ANY WARRANTY; without even the implied warranty of
  13.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.    GNU General Public License for more details.
  15.  
  16.    You should have received a copy of the GNU General Public License
  17.    along with Jade; see the file COPYING. If not, write to
  18.    the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  19.  
  20. #include "jade.h"
  21. #include "jade_protos.h"
  22.  
  23. #include <string.h>
  24.  
  25. _PR void  undo_record_deletion(TX *, POS *, POS *);
  26. _PR VALUE undo_push_deletion(TX *, POS *, POS *);
  27. _PR void  undo_record_insertion(TX *, POS *, POS *);
  28. _PR void  undo_record_modification(TX *, POS *, POS *);
  29. _PR void  undo_distinct(void);
  30. _PR void  undo_new_group(void);
  31. _PR void  undo_trim(void);
  32. _PR void  undo_init(void);
  33.  
  34. /* Maximum number of bytes that *each buffer* may devote to undo
  35.    information.  */
  36. static max_undo_size = 10000;
  37.  
  38. /* Lets us use the string which undo_record_deletion() creates for
  39.    other uses.    */
  40. static VALUE pending_deletion_string;
  41. static POS pending_deletion_start, pending_deletion_end;
  42. static TX *pending_deletion_tx;
  43.  
  44. /* A new command has been invoked but the group-separator hasn't been
  45.    added yet. */
  46. static bool pending_group;
  47.  
  48. /* While we're in cmd_undo() this is set.  This is also tested by
  49.    undo_distinct(); if FALSE it will call coalesce_undo() if necessary.
  50.    undo_distinct() always sets this to FALSE. */
  51. static bool in_undo;
  52. static TX *last_undid_tx;
  53.  
  54. static VALUE sym_undo;
  55.  
  56. /* If not in an undo, this will re-combine the waiting_undo and
  57.    tx_UndoList. */
  58. static void
  59. coalesce_undo(TX *tx)
  60. {
  61.     if(!in_undo
  62.        && (tx->tx_ToUndoList != NULL))
  63.     {
  64.     VALUE tmp = cmd_nreverse(tx->tx_UndoneList);
  65.     if(tmp)
  66.     {
  67.         tx->tx_UndoList = cmd_nconc(list_3(tx->tx_UndoList,
  68.                            tmp,
  69.                            tx->tx_ToUndoList));
  70.     }
  71.     tx->tx_UndoneList = sym_nil;
  72.     tx->tx_ToUndoList = NULL;
  73.     last_undid_tx = NULL;
  74.     }
  75. }
  76.  
  77. /* Called *after* recording an undo command, checks if this is the
  78.    first change to the buffer. Should be called before the operation. */
  79. static INLINE void
  80. check_first_mod(TX *tx)
  81. {
  82.     if((tx->tx_Changes == tx->tx_ProperSaveChanges)
  83.        && ((tx->tx_Flags & TXFF_NO_UNDO) == 0))
  84.     {
  85.     /* First modification, record this. */
  86.     tx->tx_UndoList = cmd_cons(sym_t, tx->tx_UndoList);
  87.     }
  88. }
  89.  
  90. /* If pending_group is set a group-separator is added to the buffer's
  91.    undo list. */
  92. static void
  93. check_group(TX *tx)
  94. {
  95.     if(pending_group
  96. #if 0
  97.        && !NILP(tx->tx_UndoList)
  98. #endif
  99.        && ((tx->tx_Flags & TXFF_NO_UNDO) == 0))
  100.     {
  101.     tx->tx_UndoList = cmd_cons(sym_nil, tx->tx_UndoList);
  102.     }
  103.     pending_group = FALSE;
  104. }
  105.  
  106. /* Grabs the string between START and END in buffer TX and adds it to
  107.    the buffer's undo-list.  This has to be done *before* the text is
  108.    actually deleted from the buffer (for obvious reasons).  */
  109. void
  110. undo_record_deletion(TX *tx, POS *start, POS *end)
  111. {
  112.     if((tx->tx_Flags & TXFF_NO_UNDO) == 0)
  113.     {
  114.     VALUE string;
  115.     VALUE lstart = make_lpos(start);
  116.     if((pending_deletion_string != NULL)
  117.        && (pending_deletion_tx = tx)
  118.        && (POS_EQUAL_P(&pending_deletion_start, start))
  119.        && (POS_EQUAL_P(&pending_deletion_end, end)))
  120.     {
  121.         /* A saved deletion; use it. */
  122.         string = pending_deletion_string;
  123.     }
  124.     else
  125.     {
  126.         long len = section_length(tx, start, end);
  127.         if(len == 1)
  128.         {
  129.         /* A deletion of 1 character is recorded as a character. */
  130.         string = cmd_get_char(lstart, VAL(tx));
  131.         if(!string || !NUMBERP(string))
  132.             return;
  133.         }
  134.         else
  135.         {
  136.         string = make_string(len + 1);
  137.         copy_section(tx, start, end, VSTR(string));
  138.         VSTR(string)[len] = 0;
  139.         }
  140.     }
  141.     coalesce_undo(tx);
  142.     check_group(tx);
  143.     check_first_mod(tx);
  144.     tx->tx_UndoList = cmd_cons(cmd_cons(lstart, string),
  145.                    tx->tx_UndoList);
  146.     }
  147.     pending_deletion_string = NULL;
  148. }
  149.  
  150. /* Lets the saved deletion be used for more than the undo list. Call
  151.    this *before* doing anything else. It will be copy the string and
  152.    return it. The next call to undo_record_deletion() will use the
  153.    *same* copy (unless the parameters don't match).  */
  154. VALUE
  155. undo_push_deletion(TX *tx, POS *start, POS *end)
  156. {
  157.     long len = section_length(tx, start, end);
  158.     VALUE string = make_string(len + 1);
  159.     copy_section(tx, start, end, VSTR(string));
  160.     VSTR(string)[len] = 0;
  161.     pending_deletion_string = string;
  162.     pending_deletion_start = *start;
  163.     pending_deletion_end = *end;
  164.     pending_deletion_tx = tx;
  165.     return(string);
  166. }
  167.  
  168. /* Adds an insertion between START and END to the TX buffer's undo-list.
  169.    Doesn't copy anything, just records START and END.  */
  170. void
  171. undo_record_insertion(TX *tx, POS *start, POS *end)
  172. {
  173.     if((tx->tx_Flags & TXFF_NO_UNDO) == 0)
  174.     {
  175.     VALUE item;
  176.     coalesce_undo(tx);
  177.     check_group(tx);
  178.     check_first_mod(tx);
  179.     item = tx->tx_UndoList;
  180.     if(CONSP(item) && CONSP(VCAR(item)))
  181.     {
  182.         item = VCAR(item);
  183.         if(POSP(VCDR(item)) && POS_EQUAL_P(start, &VPOS(VCDR(item))))
  184.         {
  185.         /* This insertion is directly after the end of the
  186.            previous insertion; extend the previous one to cover
  187.            this one.  */
  188.         VPOS(VCDR(item)) = *end;
  189.         return;
  190.         }
  191.     }
  192.     tx->tx_UndoList = cmd_cons(cmd_cons(make_lpos(start), make_lpos(end)),
  193.                    tx->tx_UndoList);
  194.     }
  195. }
  196.  
  197. /* Record that the text between START and END has been modified.  This
  198.    must be done *before* the modification is actually done.  */
  199. void
  200. undo_record_modification(TX *tx, POS *start, POS *end)
  201. {
  202.     if((tx->tx_Flags & TXFF_NO_UNDO) == 0)
  203.     {
  204.     undo_record_deletion(tx, start, end);
  205.     undo_record_insertion(tx, start, end);
  206.     }
  207. }
  208.  
  209. /* Signal the end of this command. */
  210. void
  211. undo_distinct(void)
  212. {
  213.     if((!last_command || !NILP(last_command))
  214.        && (last_command != sym_undo)
  215.        && last_undid_tx
  216.        && last_undid_tx->tx_ToUndoList)
  217.     {
  218.     coalesce_undo(last_undid_tx);
  219.     }
  220.     in_undo = FALSE;
  221. }
  222.  
  223. /* A new command is started. */
  224. void
  225. undo_new_group(void)
  226. {
  227.     pending_group = TRUE;
  228. }
  229.  
  230. _PR VALUE cmd_undo(VALUE tx);
  231. DEFUN_INT("undo", cmd_undo, subr_undo, (VALUE tx), V_Subr1, DOC_undo, "") /*
  232. ::doc:undo::
  233. undo [BUFFER]
  234.  
  235. In the buffer BUFFER, undo everything back to the start of the previous
  236. command. Consecutive undo commands work backwards through the BUFFER's
  237. history.
  238. ::end:: */
  239. {
  240.     if(!BUFFERP(tx))
  241.     tx = VAL(curr_vw->vw_Tx);
  242.     if(VTX(tx)->tx_ToUndoList == NULL)
  243.     {
  244.     /* First call. */
  245.     VTX(tx)->tx_ToUndoList = VTX(tx)->tx_UndoList;
  246.     VTX(tx)->tx_UndoList = sym_nil;
  247.     }
  248.     if(NILP(VTX(tx)->tx_ToUndoList))
  249.     return(cmd_signal(sym_error, LIST_1(MKSTR("Nothing to undo!"))));
  250.     in_undo = TRUE;
  251.     last_undid_tx = VTX(tx);
  252.     while(CONSP(VTX(tx)->tx_ToUndoList))
  253.     {
  254.     VALUE item = VCAR(VTX(tx)->tx_ToUndoList);
  255.     VTX(tx)->tx_ToUndoList = VCDR(VTX(tx)->tx_ToUndoList);
  256.     VTX(tx)->tx_UndoneList = cmd_cons(item, VTX(tx)->tx_UndoneList);
  257.     if(NILP(item))
  258.         break;        /* Group separator; break the loop. */
  259.     else if(CONSP(item) && POSP(VCAR(item)))
  260.     {
  261.         if(STRINGP(VCDR(item)))
  262.         {
  263.         /* A deleted string */
  264.         VALUE new = cmd_insert(VCDR(item), VCAR(item), tx);
  265.         if(new && POSP(new))
  266.             cmd_goto_char(new);
  267.         }
  268.         else if(NUMBERP(VCDR(item)))
  269.         {
  270.         /* A deleted character */
  271.         VALUE tmp = make_string(2);
  272.         VSTR(tmp)[0] = (u_char)VNUM(VCDR(item));
  273.         VSTR(tmp)[1] = 0;
  274.         tmp = cmd_insert(tmp, VCAR(item), tx);
  275.         if(tmp && POSP(tmp))
  276.             cmd_goto_char(tmp);
  277.         }
  278.         else if(POSP(VCDR(item)))
  279.         {
  280.         cmd_delete_area(VCAR(item), VCDR(item), tx); /* insert */
  281.         cmd_goto_char(VCAR(item));
  282.         }
  283.     }
  284.     else if(POSP(item))
  285.     {
  286.         cmd_goto_char(item);
  287.     }
  288.     else if(item == sym_t)
  289.     {
  290.         /* clear modification flag. */
  291.         cmd_set_buffer_modified(tx, sym_nil);
  292.     }
  293.     }
  294.     this_command = sym_undo;
  295.     return(sym_t);
  296. }
  297.  
  298. _PR VALUE var_max_undo_size(VALUE val);
  299. DEFUN("max-undo-size", var_max_undo_size, subr_max_undo_size, (VALUE val), V_Var, DOC_max_undo_size) /*
  300. ::doc:max_undo_size::
  301. The maximum amount of storage that a single buffer may devote to recording
  302. undo information.
  303. ::end:: */
  304. {
  305.     if(val)
  306.     {
  307.     if(NUMBERP(val))
  308.         max_undo_size = VNUM(val);
  309.     return(NULL);
  310.     }
  311.     else
  312.     return(make_number(max_undo_size));
  313. }
  314.  
  315. _PR VALUE var_buffer_record_undo(VALUE val);
  316. DEFUN("buffer-record-undo", var_buffer_record_undo, subr_buffer_record_undo, (VALUE val), V_Var, DOC_buffer_record_undo) /*
  317. ::doc:buffer_record_undo::
  318. When nil no undo information is kept in this buffer.
  319. ::end:: */
  320. {
  321.     TX *tx = curr_vw->vw_Tx;
  322.     if(val)
  323.     {
  324.     if(NILP(val))
  325.         tx->tx_Flags |= TXFF_NO_UNDO;
  326.     else
  327.         tx->tx_Flags &= ~TXFF_NO_UNDO;
  328.     return(NULL);
  329.     }
  330.     else
  331.     return((tx->tx_Flags & TXFF_NO_UNDO) ? sym_nil : sym_t);
  332. }
  333.  
  334. VALUE var_buffer_undo_list(VALUE val);
  335. DEFUN("buffer-undo-list", var_buffer_undo_list, subr_buffer_undo_list, (VALUE val), V_Var, DOC_buffer_undo_list) /*
  336. ::doc:buffer_undo_list::
  337. This buffer's list of undo information.
  338. ::end:: */
  339. {
  340.     TX *tx = curr_vw->vw_Tx;
  341.     if(val)
  342.     tx->tx_UndoList = val;
  343.     return(tx->tx_UndoList);
  344. }
  345.  
  346. /* Called by gc, this makes each undo-lists use less memory than
  347.    max-undo-size. But it always leaves upto the first boundary
  348.    intact.  Doesn't handle the case when the undo list is split
  349.    into three bits while in the middle of a sequence of undo's.  */
  350. void
  351. undo_trim(void)
  352. {
  353.     TX *tx = buffer_chain;
  354.     while(tx)
  355.     {
  356.     VALUE *undo_list;
  357.     long size_count = 0;
  358.     if(tx->tx_ToUndoList && !NILP(tx->tx_ToUndoList))
  359.         undo_list = &tx->tx_ToUndoList;
  360.     else if(!NILP(tx->tx_UndoneList))
  361.         undo_list = &tx->tx_UndoneList;
  362.     else
  363.         undo_list = &tx->tx_UndoList;
  364.     while(CONSP(*undo_list))
  365.     {
  366.         VALUE item = VCAR(*undo_list);
  367.         size_count += sizeof(Cons);
  368.         if(CONSP(item))
  369.         {
  370.         size_count += sizeof(Cons);
  371.         if(POSP(VCDR(item)))
  372.             size_count += sizeof(LPos) * 2;
  373.         else if(STRINGP(VCDR(item)))
  374.             size_count += STRING_LEN(VCDR(item));
  375.         if(size_count > max_undo_size)
  376.         {
  377.             /* Truncate the list at the end of this group. */
  378.             while(CONSP(*undo_list) && !NILP(VCAR(*undo_list)))
  379.             undo_list = &VCDR(*undo_list);
  380.             *undo_list = sym_nil;
  381.             break;
  382.         }
  383.         }
  384.         else if(POSP(item))
  385.         {
  386.         size_count += sizeof(LPos);
  387.         }
  388.         undo_list = &VCDR(*undo_list);
  389.     }
  390.     tx = tx->tx_Next;
  391.     }
  392. }
  393.  
  394. void
  395. undo_init(void)
  396. {
  397.     mark_static(&pending_deletion_string);
  398.     INTERN(sym_undo, "undo");
  399.     ADD_SUBR(subr_undo);
  400.     ADD_SUBR(subr_max_undo_size);
  401.     ADD_SUBR(subr_buffer_record_undo);
  402.     ADD_SUBR(subr_buffer_undo_list);
  403. }
  404.